#include "FrameRenderer.h"
#include "GLI420Renderer.h"

#ifdef __ANDROID__
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES2/gl2platform.h>

#include <memory>
#endif

namespace simple_renderer{

FrameRenderer::FrameRenderer() : _gl_renderer(nullptr),
                                 _win_w(0),
                                 _win_h(0),
                                 _surface_ready(false){
#ifdef __ANDROID__
    _frame_window_ref = nullptr;
    _window_event_wrapper_ref = nullptr;
#endif
}

FrameRenderer::~FrameRenderer(){

    _thread_pip->StopTask();
}

int FrameRenderer::StartRender(){

    _pic_frame.reset(new PicFrame);
    _thread_pip.reset(new ThreadPipe);
    _egl_impl.reset(new EglImpl);

    _thread_pip->StopTask();
    _thread_pip->StartTask();

    return 0;
}

int FrameRenderer::StopRender(){

    {
        std::unique_lock<std::mutex> lck(_draw_mux);
        _surface_ready = false;
    }

    _thread_pip->PostSync(std::bind(&FrameRenderer::DeinitGLEnvInThread, this, std::placeholders::_1), nullptr);
    //join until thread tasks end
    _thread_pip->StopTask();

    _gl_renderer = nullptr;
    _egl_impl = nullptr;

    return 0;
}

#ifdef __ANDROID__
int FrameRenderer::UpdateAndroidFrameWindow(std::shared_ptr<JavaGlobalRef<jobject>> frameCanvas, RenderParam &param){
    //update window
    _frame_window_ref = frameCanvas;

    if(_gl_renderer == nullptr || _render_param.frame_type != param.frame_type){
        //recreate renderer
        if(_gl_renderer != nullptr){
            //todo stop renderer
        }
        if(param.frame_type == kI420P){
            _gl_renderer.reset(new GLI420Renderer);
        }
    }

    _render_param = param;

    return 0;
}

void FrameRenderer::MoveIntoAndroidWindowEventWrapper(std::shared_ptr<JavaGlobalRef<jobject>> event_wrapper){
    _window_event_wrapper_ref = event_wrapper;
}

std::shared_ptr<JavaGlobalRef<jobject>> FrameRenderer::MoveOutAndroidWindowEventWrapper(){
    auto ret = _window_event_wrapper_ref;
    return ret;
}

#endif

int FrameRenderer::CreatWindow(){

    LOGV("[FrameRenderer] CreatWindow enter");

//    _thread_pip->PostSync(std::bind(&FrameRenderer::DeinitGLEnvInThread, this, std::placeholders::_1),
//                          nullptr);

    //prevent create egl conflict
    std::lock_guard<std::mutex> lck(_egl_mux);
    if(_egl_impl->IsEglValidate()){
        return 0;
    }

    int ret = _egl_impl->SetAndroidView(_frame_window_ref);
    if(!ret) {
        //if window created, start create Opengl env
        //called by surface event, should be block
        _thread_pip->PostSync(std::bind(&FrameRenderer::InitGLEnvInThread, this, std::placeholders::_1),
                          nullptr);
        _surface_ready = true;
    }else{
        _surface_ready = false;
    }
    //LOGV("[FrameRenderer] CreatWindow sync end surface_ready=%d", _surface_ready);

    LOGV("[FrameRenderer] CreatWindow leave");
    return ret;
}

int FrameRenderer::ResizeWindow(int w, int h){

    _win_w = w;
    _win_h = h;

    _thread_pip->Post(std::bind(&FrameRenderer::DrawFrameInThread, this, std::placeholders::_1),
                      nullptr);
    return 0;
}

int FrameRenderer::DestoryWindow(){

    LOGV("[FrameRenderer] DestoryWindow sync start");
    //disable next draw task
    {
        std::unique_lock<std::mutex> lck(_draw_mux);
        _surface_ready = false;
    }
    //clear all tasks
    //_thread_pip->ClearTasks();
    //clear OpenGL env sync
    //if OpenGL is drawing, block until draw over , the remove OpenGL env
    _thread_pip->PostSync(std::bind(&FrameRenderer::DeinitGLEnvInThread, this, std::placeholders::_1), nullptr);
    LOGV("[FrameRenderer] DestoryWindow sync end");

    return 0;
}

void FrameRenderer::DrawFrame(int w, int h, uint8_t *frame_data, int frame_len, FrameType type){

    LOGV("[FrameRenderer] DrawFrame start");
    if(_surface_ready) {
        {
            std::unique_lock<std::mutex> lck(_frame_mux);
            _pic_frame->CopyData(w, h, type, frame_data, frame_len);
        }
        _thread_pip->Post(std::bind(&FrameRenderer::DrawFrameInThread, this, std::placeholders::_1),
                          nullptr);
    }
    LOGV("[FrameRenderer] DrawFrame end");
}

void FrameRenderer::DrawFrameI420(int w, int h, FrameType type, uint8_t* data_y, int len_y, uint8_t* data_u, int len_u, uint8_t* data_v, int len_v){

    LOGV("[FrameRenderer] DrawFrameI420 start");
    if(_surface_ready) {
        {
            std::unique_lock<std::mutex> lck(_frame_mux);
            _pic_frame->CopyDataI420(w, h, type, data_y, len_y, data_u, len_u, data_v, len_v);
        }
        _thread_pip->Post(std::bind(&FrameRenderer::DrawFrameInThread, this, std::placeholders::_1),
                          nullptr);
    }
    LOGV("[FrameRenderer] DrawFrameI420 end");
}

void FrameRenderer::DestorySurface(){

    {
        std::unique_lock<std::mutex> lck(_draw_mux);
        _surface_ready = false;
    }
    _thread_pip->PostSync(std::bind(&FrameRenderer::DestorySurfaceInThread, this, std::placeholders::_1),
                      nullptr);
}

/**
 * should run on gl thread
 * @return
 */
void FrameRenderer::InitGLEnvInThread(MessageData * msg){
    LOGV("[FrameRenderer] InitGLEnvInThread create Opengl env start");
    if(_frame_window_ref == nullptr){
        LOGE("[FrameRenderer] InitGLEnvInThread _frame_window_ref is null!!!");
    }

    if(_egl_impl->HasContext()){
        //if has context, just recreate surface (background -> forground)
        LOGV("[FrameRenderer] InitGLEnvInThread CreateSurface");
        _egl_impl->CreateSurface();
    }else {
        LOGV("[FrameRenderer] InitGLEnvInThread InitGLEnv");
        //init egl
        _egl_impl->InitGLEnv();
        //take egl as current env
        _egl_impl->MakeCurrent();
        //init opengl
        _gl_renderer->InitRenderer();
        _egl_impl->DetachCurrent();
    }

    LOGV("[FrameRenderer] InitGLEnvInThread create Opengl env end");
}

void FrameRenderer::DrawFrameInThread(MessageData * msg){

    LOGV("[FrameRenderer] DrawFrameInThread start");

    std::unique_lock<std::mutex> lck(_draw_mux);

    if(!_surface_ready){
        LOGV("[FrameRenderer] DrawFrameInThread _surface_ready=%d", _surface_ready);
        return ;
    }
    if(!_egl_impl->IsEglValidate()){
        //to prevent a renderer reference a SurfaceView, the renderer deleted and another renderer
        //reference the same SurfaceView it maybe refernced by the earlier renderer.
        LOGE("[FrameRenderer] DrawFrameInThread egl not validate");
        int ret = _egl_impl->InitGLEnv();
        if(ret == 0){
            LOGV("[FrameRenderer] DrawFrameInThread egl reinit ok");
        }else{
            LOGV("[FrameRenderer] DrawFrameInThread egl reinit error ret=%d", ret);
            return ;
        }
        return ;
    }

    _egl_impl->MakeCurrent();

    //update surface buffer size
    _egl_impl->SetSurfaceSize(_win_w, _win_h);
    //get surface buffer size
    int egl_width = _egl_impl->GetWidth();
    int egl_height = _egl_impl->GetHeight();
    //update view port
    glViewport(0, 0, egl_width, egl_height);  GLES2_checkError("glViewport");

    _gl_renderer->ClearWindow(0.0f, 0.0f, 0.0f, 1);
    {
        if(_pic_frame != nullptr) {
            _gl_renderer->DrawPicFrame(*_pic_frame, _win_w, _win_h);
        }
    }
    _egl_impl->EGLSwap();
    _egl_impl->DetachCurrent();
    LOGV("[FrameRenderer] DrawFrameInThread end");
}

void FrameRenderer::DeinitGLEnvInThread(MessageData * msg){

    LOGV("[FrameRenderer] DeinitGLEnvInThread release Opengl env start");
    if(!_egl_impl->IsEnvReleased()){
        //if env has been released, egl would report error in _egl_impl->MakeCurrent()
        _egl_impl->MakeCurrent();
        _gl_renderer->ReleaseRenderer();

        _egl_impl->DestorySurface();
        _egl_impl->DetachCurrent();

        _egl_impl->Release();

        _egl_impl->ResetAndroidView();

        //_egl_impl->DetachCurrent();

        LOGV("[FrameRenderer] DeinitGLEnvInThread Opengl env released");
    }else{
        LOGV("[FrameRenderer] DeinitGLEnvInThread Opengl env has been released");
    }

    //_egl_impl->ResetAndroidView();

    LOGV("[FrameRenderer] DeinitGLEnvInThread release Opengl env end");
}

void FrameRenderer::DestorySurfaceInThread(MessageData * msg){

    _egl_impl->DestorySurface();
    _egl_impl->DetachCurrent();
}

}